/*
 * Copyright (C) 2012 POAS.VSTU
 */

package model;

import java.util.ArrayList;
import java.util.Iterator;
import java.util.logging.Level;
import java.util.logging.Logger;
import javax.swing.event.EventListenerList;

/** Класс игровой модели. */
public class GameModel implements Runnable{

    /// Список слушателей событий модели игры
    protected EventListenerList listenerList = new EventListenerList();

    /// Текущий статус
    private ModelStatus mCurrentStatus = ModelStatus.UNDEFINED;

    /// Колода карт
    private CardStack mStack = null;

    /// Отбой
    private CardPileRecycle mRecylce = null;

    /// Кон
    private CardPileKitty mKitty = null;

    /// Лишняя карта, убираемая в начале игры
    private CardPile mExtraCards = null;

    /// Игроки
    private CircularBuffer<Player> mPlayers;

    /// Служебный объект синхронизации
    private static final Object sLock = new Object();

    /**
     * Конструктор игровой модели.
     */
    public GameModel() {
        mStack = new CardStack();
        mRecylce = new CardPileRecycle();
        mKitty = new CardPileKitty();
        mExtraCards = new CardPile();
        mPlayers = new CircularBuffer<>();
        mPlayers.add(new Gamer("Игрок"));
        mPlayers.add(new ComputerPlayer("Трус"));
        mPlayers.add(new ComputerPlayer("Балбес"));
        mPlayers.add(new ComputerPlayer("Бывалый"));
    }

    /**
     * Получить массив всех игроков.
     *
     * Первым в массиве идет игрок-человек.
     * @return массив игроков
     */
    public CircularBuffer<Player> getAllPlayers() {
        return mPlayers;
    }

    /** Текущий статус игры. */
    public ModelStatus getStatus() {
        return mCurrentStatus;
    }

    /** Установить статус. */
    private void setStatus(ModelStatus status) {
        mCurrentStatus = status;
        if(status == ModelStatus.WAITING_FOR_PLAYER_ACTION) {
            fireUserActionWaitStartEvent();
        }
    }

    /** Колода карт. */
    public CardStack getStack() {
        return mStack;
    }

    /** Кон. */
    public CardPileKitty getKitty() {
        return mKitty;
    }

    /** Отбой. */
    public CardPileRecycle getRecycle() {
        return mRecylce;
    }

    /** Лишняя карта. */
    public CardPile getExtraCards() {
        return mExtraCards;
    }

    /**
     * Начать игру заново.
     */
    public void reset() {
        System.out.println("GameModel.reset начался");
        // Собрать карты в колоду
        moveAllCardsToCardStack();

        mStack.shuffle();
        moveExtraCardFromStack();

        dealCards();
        for (Object o: mPlayers.toArray()) {
            //System.out.println(o);
        }
        moveTetradsFromPlayersToRecycle();

        // Ожидание хода игрока
        setStatus(ModelStatus.WAITING_FOR_PLAYER_ACTION);
    }

    /**
     * Начать игру заново (версия для тестирования).
     *
     * Используется для тестирования.
     * @param testingStack колода, с которой начнется игра
     */
    public void reset(CardStack testingStack) {
        // Собрать карты в колоду
        //moveAllCardsToCardStack();

        mStack = testingStack;
        moveExtraCardFromStack();
        dealCards();
        moveTetradsFromPlayersToRecycle();


        // Ожидание хода игрока
        setStatus(ModelStatus.WAITING_FOR_PLAYER_ACTION);
    }

    /**
     * Собрать все карты в колоду на кону.
     */
    private void moveAllCardsToCardStack() {
        setStatus(ModelStatus.GATHERING_CARDS_TO_STACK);

        // Перенести карты из отбоя в стопку
        ArrayList<Card> recycleCards = mRecylce.popAll();
        if (recycleCards != null && recycleCards.size() > 0) {
            fireCardsExtractedEvent(new GameEvent(this, mRecylce, null, recycleCards));
            fireCardsMovedEvent(new GameEvent(this, mRecylce, mStack, recycleCards));
            mStack.push(recycleCards);
            fireCardsAddedEvent(new GameEvent(this, null, mStack, recycleCards));
        }


        // Перенести лишнюю карту в стопку
        ArrayList<Card> extraCards = mExtraCards.popAll();
        if (extraCards != null && extraCards.size() > 0) {
            fireCardsExtractedEvent(new GameEvent(this, mExtraCards, null, extraCards));
            fireCardsMovedEvent(new GameEvent(this, mExtraCards, mStack, extraCards));
            mStack.push(extraCards);
            fireCardsAddedEvent(new GameEvent(this, null, mStack, extraCards));
        }

        // Перенести карты с кона в стопку
        ArrayList<Card> kittyCards = mKitty.popAll();
        if (kittyCards != null && kittyCards.size() > 0) {
            fireCardsExtractedEvent(new GameEvent(this, mKitty, null, kittyCards));
            fireCardsMovedEvent(new GameEvent(this, mKitty, mStack, kittyCards));
            mStack.push(kittyCards);
            fireCardsAddedEvent(new GameEvent(this, null, mStack, kittyCards));
        }


        // Перенести карты всех игроков в стопку
        for (Object o: mPlayers.toArray()) {
            Player p = (Player) o;
            ArrayList<Card> playerCards = p.getCards().popAll();
            if (playerCards != null && playerCards.size() > 0) {
                fireCardsExtractedEvent(new GameEvent(this, p.getCards(), null, playerCards));
                fireCardsMovedEvent(new GameEvent(this, p.getCards(), mStack, playerCards));
                mStack.push(playerCards);
                fireCardsAddedEvent(new GameEvent(this, null, mStack, playerCards));
            }
        }
        fireAllCardsInStackEvent();
    }

    /**
     * Переместить первую карту из колоды.
     *
     * Испускает событие типа GameEvent.
     */
    private void moveExtraCardFromStack() {
        // Установить статус
        setStatus(ModelStatus.DROPPING_FIRST_CARD);

        // Сбросить верхнюю карту из колоды
        Card first = mStack.pop();
        if (first != null) {
            fireCardsExtractedEvent(new GameEvent(this, mStack, null, first));

            // Сигнал "карта перемещается"
            fireCardsMovedEvent(new GameEvent(this, mStack, mExtraCards, first));

            // Добавить лишнюю карту
            mExtraCards.push(first);
            fireCardsAddedEvent(new GameEvent(this, null, mExtraCards, first));
        }

    }

    /**
     * Раздать карты игрокам.
     */
    private void dealCards() {
        setStatus(ModelStatus.DEALING_CARDS);

        Iterator<Player> playerIterator = mPlayers.iterator();
        Card card = null;
        // Раздать карты игрокам
        while ((card = mStack.pop()) != null) {
            Player player = playerIterator.next();
            PlayerCardPile pcf = player.getCards();
            Card c = card;
            fireCardsExtractedEvent(new GameEvent(this, mStack, null, c));

            // Испустить сигнал перемещения карты
            fireCardsMovedEvent(new GameEvent(this, mStack, pcf, c));

            // Добавить карту
            player.getCards().push(card);
            fireCardsAddedEvent(new GameEvent(this, null, pcf, c));
        }
    }

    public void cardsToRecycle() {
        setStatus(ModelStatus.DEALING_CARDS);

        // todo just for test -- comment in in release
        mStack.shuffle();
        // [/todo]

        int cardsCount = 1;
        for (int i = 0; i < cardsCount; i++) {
            System.out.println("cardsToRecycle -- " + i);

            Card card = mStack.pop();
            if (card == null) continue;

            fireCardsExtractedEvent(new GameEvent(this, mStack, null, card));
            
            fireCardsMovedEvent(new GameEvent(this, mStack, mRecylce, card));

            // add card
            mRecylce.push(card);
            fireCardsAddedEvent(new GameEvent(this, null, mRecylce, card));
        }

        setStatus(ModelStatus.WAITING_FOR_PLAYER_ACTION);
    }

    /**
     * Сбросить четверки карт всех игроков в отбой.
     * @return количество сброшенных в отбой карт
     */
    private void moveTetradsFromPlayersToRecycle() {
        Object[] players = mPlayers.toArray();
        for (int i = 0; i < players.length; i++) {
            Player player = (Player) players[i];
            // Сбросить все четверки карт игрока

            ArrayList<Card> tetrad = player.getCards().popTetrad();
            while (tetrad != null && tetrad.size() > 0) {
                // Установить статус
                setStatus(ModelStatus.DROPPING_TETRAD);
                // Сигнал, что карты перемещаются
                fireCardsExtractedEvent(new GameEvent(this, mStack, null, tetrad));

                // Карты перемещаются
                fireCardsMovedEvent(new GameEvent(this, mStack, mRecylce, tetrad));

                // Переместить карты в отбой
                mRecylce.push(tetrad);
                fireCardsAddedEvent(new GameEvent(this, null, mRecylce, tetrad));

                tetrad = player.getCards().popTetrad();
            }
        }
    }

    /** Разбудить поток модели. */
    public void wakeUp() {
        synchronized(sLock) {
            sLock.notifyAll();
        }
    }

    /** Приостановить выполнение потока модели. */
    private void sleep() {
        synchronized(sLock) {
            try {
                sLock.wait();
            } catch (InterruptedException ex) {
                Logger.getLogger(GameModel.class.getName()).log(Level.SEVERE, null, ex);
            }
        }
    }

    /**
     * Испустить событие "карты перемещаются".
     * @param event испускаемое событие
     */
    private void fireCardsMovedEvent(GameEvent event) {
        Object[] listeners = listenerList.getListeners(GameEventListener.class);
        for (int i = 0; i < listeners.length; i++) {
            ((GameEventListener)listeners[i]).cardsMoved(event);
        }
    }

    /**
     * Испустить событие "карты извлечены".
     * @param event испускаемое событие
     */
    private void fireCardsExtractedEvent(GameEvent event) {
        Object[] listeners = listenerList.getListeners(GameEventListener.class);
        for (int i = 0; i < listeners.length; i++) {
            ((GameEventListener)listeners[i]).cardsExtracted(event);
        }
    }

    /**
     * Испустить событие "карты добавлены".
     * @param event испускаемое событие
     */
    private void fireCardsAddedEvent(GameEvent event) {
        Object[] listeners = listenerList.getListeners(GameEventListener.class);
        for (int i = 0; i < listeners.length; i++) {
            ((GameEventListener)listeners[i]).cardsAdded(event);
        }
    }
    
    /**
     * Испустить событие "модель ожидает действие игрока"
    */
    private void fireUserActionWaitStartEvent() {
        Object[] listeners = listenerList.getListeners(GameEventListener.class);
        for (int i = 0; i < listeners.length; i++) {
            ((GameEventListener)listeners[i]).userActionWaitStart();
        }
    }
    
    /**
     * Испустить событие "все карты собраны в колоду"
    */
    private void fireAllCardsInStackEvent() {
        Object[] listeners = listenerList.getListeners(GameEventListener.class);
        for (int i = 0; i < listeners.length; i++) {
            ((GameEventListener)listeners[i]).allCardsInStack();
        }
    }
    
    
 

    /**
     * Добавить слушателя событий.
     * @param l добавляемый слушатель событий
     */
    public void addGameEventListener(GameEventListener l) {
        listenerList.add(GameEventListener.class, l);
    }

    /**
     * Удаление слушателя событий.
     * @param l слушатель, которого нужно забыть
     */
    public void removeGameEventListener(GameEventListener l) {
        listenerList.remove(GameEventListener.class, l);
    }

    @Override
    public void run() {

    }
}
